/**
 * @author Da Mu
 * @version 0.1.0
 * @mail 317782199@qq.com
 */
import { ConfigService } from '@nestjs/config';
import { Injectable, HttpStatus, HttpException, NotFoundException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { v4 as uuidv4 } from 'uuid';
import { TemplateApiLoginJWTDto, TemplateApiEmail, TemplateApiSavePlayerLocaDataDto } from './TemplateApi.dto';
import { TemplateApiDefaultService, TemplateApiLoginFallService } from './TemplateApi.entity';
import { TemplateApiRedisService } from './TemplateApiPlugService/TemplateApiRedis.service';
import { TemplateApiSvgCaptchaService } from './TemplateApiPlugService/TemplateApiSvgCode.service';
import { TemplateApiQrService } from './TemplateApiPlugService/TemplateApiQr.service';
import { TemplateApiMailService } from './TemplateApiPlugService/TemplateApiEmail.service';

import { BullMQService } from './TemplateApiPlugService/TemplateApiBullmq.service';

import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration'

import calculateTimeRemaining from '../utils/calculateTimeRemaining';

import * as crypto from 'crypto';

import * as jsonfile from 'jsonfile';
import { join } from 'path';
import * as fs from 'fs';

interface fallItems {
  uuid: string,
  type: string
};

class TimerServer {
  static getFutureTime(minutesToAdd: number): dayjs.Dayjs {
    return dayjs().add(minutesToAdd, 'minute');
  };
  static getTimeRemaining(futureTime: dayjs.Dayjs | Date | string): any {
    const future = dayjs(futureTime);
    const now = dayjs();

    return dayjs.duration(future.diff(now));
  }
};

class RangeRandomDistribution {
  static place(radius: number, center: { x: number, z: number }, number: { min: number, max: number }) {
    const random = crypto.randomInt(number.min, number.max);
    let coor: Array<object> = [];

    for (let i: number = 0; i < random; i++) {
      const angle = Math.random() * Math.PI * 2;
      /** Randomly generate a radius (0 to spawnRadius) */
      const randomRadius = Math.sqrt(Math.random()) * radius; // Use SQRT to ensure even distribution
      /** Convert polar coordinates to Cartesian coordinates */
      const x = center.x + randomRadius * Math.cos(angle);
      const z = center.z + randomRadius * Math.sin(angle);
      coor.push({ x, z });
    };

    return {
      number: random,
      coordinate: coor
    };
  };
};

@Injectable()
export class TemplateApiService {
  private readonly filePath: string = join(process.cwd(), 'static/game', 'GaemCoordinates.json');
  private readonly filePath_timer: string = join(process.cwd(), 'static/game', 'Timer.json');
  private readonly filePath_playerInfo: string = join(process.cwd(), 'static/game', 'PlayerInfo-dev.json');
  private readonly filePath_enable3d: string = join(process.cwd(), 'static/game', 'Enable3DSceneModels.json');
  advocado: Map<number, object>
  dinner: Map<number, object>
  drink: Map<number, object>
  filePath_advocado: string
  filePath_dinner: string
  filePath_drink: string
  base64StringAdvocado: string
  base64StringDinner: string
  base64StringDrink: string
  constructor(
    private readonly config: ConfigService,
    private readonly jwt: JwtService,
    private readonly redis: TemplateApiRedisService,
    private readonly svgcap: TemplateApiSvgCaptchaService,
    private readonly qr: TemplateApiQrService,
    private readonly mail: TemplateApiMailService,
    private readonly mq: BullMQService
  ) {
    dayjs.extend(duration);
    this.advocado = new Map();
    this.dinner = new Map();
    this.drink = new Map();

    this.filePath_advocado = join(process.cwd(), 'static/game/images', 'advocado-half.png');
    this.filePath_dinner = join(process.cwd(), 'static/game/images', 'plate-dinner.png');
    this.filePath_drink = join(process.cwd(), 'static/game/images', 'soda.png');
    this.base64StringAdvocado = fs.readFileSync(this.filePath_advocado, { encoding: 'base64' });
    this.base64StringDinner = fs.readFileSync(this.filePath_dinner, { encoding: 'base64' });
    this.base64StringDrink = fs.readFileSync(this.filePath_drink, { encoding: 'base64' });
  };

  async JWT(query: TemplateApiLoginJWTDto): Promise<TemplateApiDefaultService> {
    const data = this.jwt.sign({ userId: query.id }, {
      secret: this.config.get('template.secret'),
      expiresIn: this.config.get('template.expiresIn')
    });

    return {
      code: HttpStatus.OK,
      message: data
    };
    // throw new HttpException(data, HttpStatus.INTERNAL_SERVER_ERROR);
  };

  async svgcode(query: any): Promise<TemplateApiDefaultService> {
    const svgCaptcha = await this.svgcap.captche();
    this.redis.set('r_svg_code', svgCaptcha.text, 60 * 30);

    return {
      code: HttpStatus.OK,
      message: svgCaptcha.data
    };
  };

  async svgcodevalidate(code: string): Promise<TemplateApiDefaultService> {
    const r_svg_code = await this.redis.get('r_svg_code');

    if (code === r_svg_code) {
      return {
        code: HttpStatus.OK,
        message: 'svg图形验证-验证器通过'
      };
    } else {
      throw new HttpException('验证码错误', HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async qrcode(query: any): Promise<TemplateApiDefaultService> {
    const qr = await this.qr.code('https://gitee.com/mu1866');

    return {
      code: HttpStatus.OK,
      message: qr
    };
  };

  async email(body: TemplateApiEmail): Promise<TemplateApiDefaultService> {
    try {
      await this.mail.sendVerificationCode(body.email, body.code);

      return {
        code: HttpStatus.OK,
        message: 'email-发送成功'
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  /** ----------------------------------- Game Controller Service ------------------------------------------- */

  async startGame(query: { playerUID: string }): Promise<{ code: number, message: object }> {
    try {

      const body = await this.redis.hgetall(query.playerUID);

      this.mq.startGame(query.playerUID);

      return {
        code: HttpStatus.OK,
        message: { body: body }
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async savePlayerStatusDataService(body: any): Promise<{ code: number, message: string }> {
    try {

      await this.redis.hset('all_online_players_uid', body.playerUID, JSON.stringify({
        uid: body.playerUID,
        lginTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
      }));

      await this.redis.hset(body.playerUID, 'players_status_info', JSON.stringify(body));

      return {
        code: HttpStatus.OK,
        message: ''
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async savePlayersInfoForFirst(body: TemplateApiSavePlayerLocaDataDto): Promise<object> {
    try {

      await this.redis.hset(body.playerUID, 'players_info', JSON.stringify(body));

      return {
        code: HttpStatus.OK,
        message: ''
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async playersBuildingStatus(body: { playerName?: string, playerUID: string, playerBuild: number, playerBuildProgress: boolean, type: string }) {
    const { playerName, playerUID, playerBuild, playerBuildProgress } = body;

    try {
      if (body.type === 'save') {

        this.redis.hset(playerUID, 'player_status_build', JSON.stringify({
          playerUID: playerUID,
          playerName: playerName || 'TEST Player',
          playerBuild: playerBuild,
          playerBuildProgress: playerBuildProgress
        }));

        return {
          code: HttpStatus.OK,
          message: ''
        };

      } else {

        const data = await this.redis.hgetall(playerUID);
        return {
          code: HttpStatus.OK,
          message: data
        };

      }
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async playersIndustryStatus(body: { playerName?: string, playerUID: string, wood?: number, stone?: number, type: string }) {
    const { playerName, playerUID, wood, stone } = body;

    try {
      if (body.type === 'save') {
        if (playerName && playerUID) {
          this.redis.hset(playerUID, 'player_industry_info', JSON.stringify({
            playerUID: playerUID,
            playerName: playerName || 'TEST Player',
            wood: wood || 0,
            stone: stone || 0
          }));

          return {
            code: HttpStatus.OK,
            message: ''
          };
        } else {
          return {
            code: HttpStatus.OK,
            message: 'your type save? why no playerName&playerUID&playerBuild?'
          };
        };
      } else {

        const data = await this.redis.hgetall(playerUID);
        return {
          code: HttpStatus.OK,
          message: data
        };
      }
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async playersFoodStatus(body: { playerName?: string, playerUID: string, advocado: number, dinner: number, drink: number, type: string }) {
    const { playerName, playerUID, advocado, dinner, drink } = body;

    try {
      if (body.type === 'save') {
        if (playerName && playerUID) {
          this.redis.hset(playerUID, 'player_food_info', JSON.stringify({
            playerUID: playerUID,
            playerName: playerName || 'TEST Player',
            dinner: dinner || 0,
            advocado: advocado || 0,
            drink: drink || 0
          }));

          return {
            code: HttpStatus.OK,
            message: ''
          };
        } else {
          return {
            code: HttpStatus.OK,
            message: 'your type save? why no playerName&playerUID?'
          };
        };
      } else {

        const data = await this.redis.hgetall(playerUID);
        return {
          code: HttpStatus.OK,
          message: data
        };
      }
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getUiStatusTipImageItem() {
    try {

      const filePath_Air = join(process.cwd(), 'static/game/images', 'commercial-airplane.png');
      const filePath_fallout = join(process.cwd(), 'static/game/images', 'fallout-shelter.png');
      const filePath_stomach = join(process.cwd(), 'static/game/images', 'stomach.png');
      const filePath_bottle = join(process.cwd(), 'static/game/images', 'soda-bottle.png');
      if (!fs.existsSync(filePath_Air) && !fs.existsSync(filePath_fallout) && !fs.existsSync(filePath_stomach) && !fs.existsSync(filePath_bottle)) throw new NotFoundException('file not found');
      const base64StringAir = fs.readFileSync(filePath_Air, { encoding: 'base64' });
      const base64StringFallout = fs.readFileSync(filePath_fallout, { encoding: 'base64' });
      const base64StringStomach = fs.readFileSync(filePath_stomach, { encoding: 'base64' });
      const base64StringBottle = fs.readFileSync(filePath_bottle, { encoding: 'base64' });

      return {
        code: HttpStatus.OK,
        message: [{
          name: '空投',
          id: 'ui-status-item-qcVdFmaQ',
          img: 'data:image/png;base64,' + base64StringAir,
          progress: 0
        }, {
          name: '毒雾',
          id: 'ui-status-item-pawZggVy',
          img: 'data:image/png;base64,' + base64StringFallout,
          progress: 0
        }, {
          name: '饥饿',
          id: 'ui-status-item-jqHWPeAq',
          img: 'data:image/png;base64,' + base64StringStomach,
          progress: 0
        }, {
          name: '饥渴',
          id: 'ui-status-item-VuJEkVkD',
          img: 'data:image/png;base64,' + base64StringBottle,
          progress: 0
        }]
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getUiCameraSwitchImageItem() {
    try {

      const filePath_Camera = join(process.cwd(), 'static/game/images', 'photo-camera.png');
      if (!fs.existsSync(filePath_Camera)) throw new NotFoundException('file not found');
      const base64StringCamera = fs.readFileSync(filePath_Camera, { encoding: 'base64' });

      return {
        code: HttpStatus.OK,
        message: [{
          name: '区域地图',
          id: 'ui-camera-item-imDN1z',
          img: 'data:image/png;base64,' + base64StringCamera,
          progress: 0
        }, {
          name: '运输机',
          id: 'ui-camera-item-h58zJm',
          img: 'data:image/png;base64,' + base64StringCamera,
          progress: 0
        }, {
          name: '各个儿',
          id: 'ui-camera-item-kKZpYH',
          img: 'data:image/png;base64,' + base64StringCamera,
          progress: 0
        }]
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async getUibuildImageItem() {
    try {

      const filePath_Home = join(process.cwd(), 'static/game/images', 'camping-tent.png');
      const filePath_Stone = join(process.cwd(), 'static/game/images', 'stone-pile.png');
      const filePath_Wood = join(process.cwd(), 'static/game/images', 'wood-pile.png');
      if (!fs.existsSync(filePath_Home) && !fs.existsSync(filePath_Stone) && !fs.existsSync(filePath_Wood)) throw new NotFoundException('file not found');
      const base64StringHome = fs.readFileSync(filePath_Home, { encoding: 'base64' });
      const base64StringStone = fs.readFileSync(filePath_Stone, { encoding: 'base64' });
      const base64StringWood = fs.readFileSync(filePath_Wood, { encoding: 'base64' });

      return {
        code: HttpStatus.OK,
        message: [{
          name: '安全屋',
          id: 'ui-build-item-UlhJPh',
          img: 'data:image/png;base64,' + base64StringHome,
          type: 'countdown',
          progress: 0
        }, {
          name: '矿石',
          id: 'ui-build-item-fOiXjm',
          img: 'data:image/png;base64,' + base64StringStone,
          type: 'count_stone',
          progress: 0
        }, {
          name: '木材',
          id: 'ui-build-item-J053EW',
          img: 'data:image/png;base64,' + base64StringWood,
          type: 'count_wood',
          progress: 0
        }]
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async getAllGameCoordinate(query: any) {
    try {
      const json = await jsonfile.readFileSync(this.filePath);

      switch (query.sw) {
        case 'GameRegionRadiation':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'GameRegionMapTag':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'StaticModelGltfPartial':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'StaticModelGltf':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'GameAirdropModelGltfOfSpacecraft':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'GameAirdropBoxModelGltfOfSpacecraft':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'UiCameraSwitchGameRegionMap':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'GameAirdropBoxModelMovePathAnima':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
        case 'GameAirdropModelMovePathAnima':
          return {
            code: HttpStatus.OK,
            message: json[query.sw]
          };
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getUiBuildTimer(query: { playerUID: string }) {

    try {
      const j: any = await this.redis.hgetall(query.playerUID);
      const d = JSON.parse(j.player_status_build);
      let k = JSON.parse(j.player_industry_info);

      if (k.wood < 50 || k.stone < 100) return {
        code: 700,
        message: '耗材不够啊...'
      };

      if (d.playerBuildProgress) return {
        code: 900,
        message: '安全屋正在建造中...'
      };

      if (d.playerBuild === 1) return {
        code: 800,
        message: '安全屋建造完成'
      };

      const json = await jsonfile.readFileSync(this.filePath_timer);

      const futureTime = TimerServer.getFutureTime(json.ui_build.home_m);
      const remaining = TimerServer.getTimeRemaining(futureTime);

      this.redis.hset(query.playerUID, 'player_build_timer', JSON.stringify({
        playerUID: query.playerUID,
        remaining: futureTime.format('YYYY-MM-DD HH:mm:ss')
      }));

      d.playerBuildProgress = true;
      this.redis.hset(query.playerUID, 'player_status_build', JSON.stringify(d))

      k.wood -= 50;
      k.stone -= 100;

      await this.redis.hset(query.playerUID, 'player_industry_info', JSON.stringify(k));

      return {
        code: HttpStatus.OK,
        message: remaining.asMilliseconds()
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getUiStatusTimer() {
    try {
      const json = await jsonfile.readFileSync(this.filePath_timer);

      return {
        code: HttpStatus.OK,
        message: json
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async getPlayerInitInfoDev() {
    try {
      const json = await jsonfile.readFileSync(this.filePath_playerInfo);

      return {
        code: HttpStatus.OK,
        message: json
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async getPlayerBuildingRemainingTime(query: { playerUID: string }) {

    try {
      const j: any = await this.redis.hgetall(query.playerUID);

      /**@description check playerBuildProgress */
      const d = JSON.parse(j.player_status_build);
      if (!d.playerBuildProgress) return {
        code: 800,
        message: 0
      };
      /**@description check remaining */
      const t = JSON.parse(j.player_build_timer);
      if (dayjs().isAfter(t.remaining)) return {
        code: 900,
        message: 0
      };

      const time = calculateTimeRemaining.diff(t.remaining, dayjs().format('YYYY-MM-DD HH:mm:ss'));
      if (time.totalMilliseconds === 0) return {
        code: 900,
        message: 0
      };

      return {
        code: HttpStatus.OK,
        message: time
      };


    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getPlayerIndustryCount(query: { playerUID: string }) {
    try {
      const json = await this.redis.hgetall(query.playerUID);

      return {
        code: HttpStatus.OK,
        message: json
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getEnable3DSceneModels() {

    try {
      const json = await jsonfile.readFileSync(this.filePath_enable3d);

      return {
        code: HttpStatus.OK,
        message: json
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getRangeRandomDistribution(query: { radius: number }) {
    try {
      const data = RangeRandomDistribution.place(query.radius, { x: 0, z: 0 }, { min: 0, max: 5 });

      return {
        code: HttpStatus.OK,
        message: data
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };
  };

  async playersPickUpMaterials(body: { type: string, playerUID: string }) {

    try {
      const json = await this.redis.hgetall(body.playerUID);
      let industry = JSON.parse(json.player_industry_info);
      let food = JSON.parse(json.player_food_info);

      if (body.type === 'wood') {
        industry.wood += 1;
      };

      if (body.type === 'stone') {
        industry.stone += 1;
      };

      if (body.type === 'advocado') {
        food.advocado += 1;
      };

      if (body.type === 'dinner') {
        food.dinner += 1;
      };

      if (body.type === 'soda') {
        food.drink += 1;
      };

      if (body.type === 'wood' || body.type === 'stone') {
        const res = await this.redis.hset(body.playerUID, 'player_industry_info', JSON.stringify(industry));
        return {
          code: HttpStatus.OK,
          message: res
        };
      };

      if (body.type === 'advocado' || body.type === 'dinner' || body.type === 'soda') {
        const res = await this.redis.hset(body.playerUID, 'player_food_info', JSON.stringify(food));
        return {
          code: HttpStatus.OK,
          message: res
        };
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async playersEatFood(body: { type: string, playerUID: string }) {

    try {
      const json = await this.redis.hgetall(body.playerUID);
      let food = JSON.parse(json.player_food_info);

      if (body.type === 'advocado') {
        food.advocado > 0 ? food.advocado -= 1 : food.advocado = 0;
      };

      if (body.type === 'dinner') {
        food.dinner > 0 ? food.dinner -= 1 : food.dinner = 0;
      };

      if (body.type === 'soda') {
        food.drink > 0 ? food.drink -= 1 : food.drink = 0;
      };

      if (body.type === 'advocado' || body.type === 'dinner' || body.type === 'soda') {
        const res = await this.redis.hset(body.playerUID, 'player_food_info', JSON.stringify(food));
        return {
          code: HttpStatus.OK,
          message: res
        };
      };

    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };

  async getBackpackItems(query: { playerUID: string }) {

    try {

      this.advocado = new Map();
      this.dinner = new Map();
      this.drink = new Map();

      const j: any = await this.redis.hgetall(query.playerUID);
      let food = JSON.parse(j.player_food_info);

      /**@description advocado items */
      for (let i = 0; i < food.advocado; i++) {
        this.advocado.set(i, {
          name: '牛油果',
          className: 'backPackItems',
          classNameOth: false,
          image: 'data:image/png;base64,' + this.base64StringAdvocado || null,
          describe: {
            title: {
              txt: '牛油果',
              col: '#ffffff'
            },
            type: {
              txt: '普通消耗品 · 水果'
            },
            des: {
              txt: '"牛油果好吃嘛..."'
            },
            val: 5,
            bor: '2px solid #ffffff',
            showUse: true,
            showSell: true,
            showPut: false
          }
        });
      };
      /**@description dinner item */
      for (let i = 0; i < food.dinner; i++) {
        this.dinner.set(i, {
          name: '晚餐',
          className: 'backPackItems-legend',
          classNameOth: true,
          image: 'data:image/png;base64,' + this.base64StringDinner || null,
          describe: {
            title: {
              txt: '晚餐',
              col: '#ff6a00'
            },
            type: {
              txt: '传说消耗品 · 热乎乎的饭'
            },
            des: {
              txt: '"来自家里准备的热乎乎饭菜，有张便条「"吃完饭在出去玩！"」"'
            },
            val: 500,
            bor: '2px solid #ff6a00',
            showUse: true,
            showSell: true,
            showPut: false
          }
        });
      };
      /**@description drink items */
      for (let i = 0; i < food.drink; i++) {
        this.drink.set(i, {
          name: '饮料',
          className: 'backPackItems',
          classNameOth: false,
          image: 'data:image/png;base64,' + this.base64StringDrink || null,
          describe: {
            title: {
              txt: '饮料',
              col: '#ffffff'
            },
            type: {
              txt: '普通消耗品 · 小甜水'
            },
            des: {
              txt: '"橙汁..."'
            },
            val: 5,
            bor: '2px solid #ffffff',
            showUse: true,
            showSell: true,
            showPut: false
          }
        });
      };

      const bakcPackItems = [Array.from(this.drink.values()), Array.from(this.dinner.values()), Array.from(this.advocado.values())];

      return {
        code: HttpStatus.OK,
        msg: bakcPackItems,
      };
    } catch (error) {
      throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
    };

  };


};
